﻿using HybridCLR.Editor;
using HybridCLR.Editor.AOT;
using HybridCLR.Editor.Commands;
using HybridCLR.Editor.Installer;
using HybridCLR.Editor.Meta;
using HybridCLR.Editor.Settings;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System; 
using System.Collections.Generic;
using System.IO;
using System.Linq; 
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using XFABManager;
using XFGameFramework.HybridCLR; 

public class ListenerXFProjectBuild : IPostprocessBuildProject, IPreprocessBuildProject
{
    public void OnPostprocess(string projectName, string outputPath, BuildTarget buildTarget)
    {
        //Debug.Log("打包后调用!");
    }

    public void OnPreprocess(string projectName, string outputPath, BuildTarget buildTarget)
    {
        //Debug.Log("打包前调用"); 
        XFHybridCLRSettings.Instance.SyncProjects(); 
        XFHybridCLRSettings.Instance.SyncHotUpdateSettings();

        HybridCLRSettingInfo info = XFHybridCLRSettings.Instance.GetSettingInfo(projectName);

        if (info == null) return;

        List<AssemblyDefinitionAsset> assemblies = info.GetAssemblies();
        List<DefaultAsset> dlls = info.GetDlls();

       

        InstallHybrid();

        // GenerateAll
        EditorUtility.DisplayProgressBar("HybridCLR ... ", "正在生成热更程序集...", 1);


        // 生成 Dlls
        LinkGeneratorCommand.GenerateLinkXml();
        Il2CppDefGeneratorCommand.GenerateIl2CppDef(); 
        StripAOTDllCommand.GenerateStripedAOTDlls(); 
        MethodBridgeGeneratorCommand.GenerateMethodBridgeAndReversePInvokeWrapper();
        AOTReferenceGeneratorCommand.CompileAndGenerateAOTGenericReference();
         
        
        EditorUtility.ClearProgressBar();
        // 把dll 和 元数据 复制到工程中的某一个目录 
        string hotUpdateDllDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(buildTarget);
        hotUpdateDllDir = string.Format("{0}/../{1}", Application.dataPath, hotUpdateDllDir);


        // 由程序集生成的dll
        List<string> generate_dlls = new List<string>();

        foreach (var item in assemblies)
        { 
            JObject obj = JsonConvert.DeserializeObject<JObject>(item.text);
            generate_dlls.Add(string.Format("{0}.dll", obj["name"]));
        }
        // 复制dll
        CopyDll(hotUpdateDllDir, string.Format("{0}/{1}", Const.HotUpdateDllDir , projectName), generate_dlls);
        // 复制元数据dll
        CopyMetadataDll(buildTarget,projectName);

        // 复制外部的dll 
        foreach (var item in dlls)
        {
            string path = AssetDatabase.GetAssetPath(item);
            if (string.IsNullOrEmpty(path)) continue;
            string name = Path.GetFileName(path);
            string dest_path = string.Format("{0}/{1}/{2}.bytes", Const.HotUpdateDllDir,projectName, name);
            File.Copy(path, dest_path, true);
        }

        // 复制加载顺序 
        File.WriteAllText(string.Format("{0}/{1}/{2}.txt", Const.HotUpdateDllDir,projectName,HybridCLRConst.ASSEMBLY_LOADING_ORDER),XFHybridCLRSettings.Instance.GetLoadingOrders(projectName));

        // 添加 ab 包 
        string dir = Const.HotUpdateDllDir.Replace(Application.dataPath, "Assets");
        AddAssetBundle(projectName, HybridCLRConst.HOT_UPDATE_DLL_BUNDLE_NAME, string.Format("{0}/{1}", dir,projectName) );

        dir = Const.MetadataDllDir.Replace(Application.dataPath, "Assets");
        AddAssetBundle(projectName, HybridCLRConst.METADATA_DLL_BUNDLE_NAME, string.Format("{0}/{1}", dir, projectName)); 
    }


    private static void InstallHybrid()
    {
        InstallerController _controller = new InstallerController();
        // 如果已安装 直接return
        if (_controller.HasInstalledHybridCLR())
            return;
        EditorUtility.DisplayProgressBar("HybridCLR Installer ... ", "正在安装 Hybrid ...", 1);
        _controller.InstallDefaultHybridCLR();
        EditorUtility.ClearProgressBar();
    }


    private static void CopyDll(string source_dir, string target_dir,IList<string> files)
    { 
        // 清空目标文件夹的内容
        if (Directory.Exists(target_dir)) 
            Directory.Delete(target_dir, true); 
         
        Directory.CreateDirectory(target_dir);

        foreach (var item in files)
        {
            string filePath = string.Format("{0}/{1}", source_dir, item);
            if (!File.Exists(filePath)) 
            { 
                string message = string.Format("未发现dll文件:{0}!", filePath); 
                throw new System.Exception(message);
            } 
            string targetPath = string.Format("{0}/{1}.bytes",target_dir, item);
            File.Copy(filePath, targetPath, true);
        }
         
        AssetDatabase.Refresh();
    }

    private static void CopyMetadataDll(BuildTarget buildTarget,string projectName) 
    {
        AssetDatabase.Refresh();
 
        string metadll = SettingsUtil.GetAssembliesPostIl2CppStripDir(buildTarget);
        metadll = string.Format("{0}/../{1}", Application.dataPath, metadll);

        CopyDll(metadll,string.Format("{0}/{1}", Const.MetadataDllDir,projectName), GenerateAOTGenericReference(buildTarget)); 
    }


    private static void AddAssetBundle(string projectName,string bundleName,string dir) 
    {
        if (!AssetDatabase.IsValidFolder(dir))
            throw new Exception(string.Format("无效的目录:{0}",dir));

        XFABProject project = XFABProjectManager.Instance.GetProject(projectName);

        if (project == null)
            throw new Exception(string.Format("未查询到资源模块:{0}", projectName));

        XFABAssetBundle bundle = project.GetAssetBundle(bundleName);
        if (bundle == null)
        {
            bundle = new XFABAssetBundle(bundleName, projectName);
            project.AddAssetBundle(bundle);
            
        }

        string guid = AssetDatabase.AssetPathToGUID(dir);
          
        if (!bundle.IsContainFile(guid)) 
        { 
            bundle.AddFile(dir);
        }

        project.Save();
    }


    private static List<string> GenerateAOTGenericReference(BuildTarget target)
    {
        List<string> generics = new List<string>();

        var gs = SettingsUtil.HybridCLRSettings;
        List<string> hotUpdateDllNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved;
         
        AssemblyReferenceDeepCollector collector = new AssemblyReferenceDeepCollector(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(target, hotUpdateDllNames), hotUpdateDllNames);
        var analyzer = new Analyzer(new Analyzer.Options
        {
            MaxIterationCount = Math.Min(20, gs.maxGenericReferenceIteration),
            Collector = collector,
        });

        analyzer.Run();

        List<dnlib.DotNet.ModuleDef> modules = new HashSet<dnlib.DotNet.ModuleDef>(
         analyzer.AotGenericTypes.Select(t => t.Type.Module).Concat(analyzer.AotGenericMethods.Select(m => m.Method.Module))).ToList();
        modules.Sort((a, b) => a.Name.CompareTo(b.Name));
        foreach (dnlib.DotNet.ModuleDef module in modules)
        {
            generics.Add(module.Name);
        }

        return generics;

    }

}
